package simpledb.buffer;

class BasicBufferMgr {
	private Buffer[] bufferpool;
	private Buffer availableHead;
	private Buffer availableTail;
	
	private int hit;
	private int total;

	BasicBufferMgr(int numbuffs) {
		bufferpool = new Buffer[numbuffs];
		Buffer tmp = null;
		
		for (int i=0; i<numbuffs; i++){
			bufferpool[i] = new Buffer();
			if(tmp != null){
				bufferpool[i].setPreAvailable(tmp);
				tmp.setNextAvailable(bufferpool[i]);
			}
			tmp = bufferpool[i];
		}
			
		availableHead = bufferpool[0];
		availableTail = bufferpool[numbuffs-1];
		
		hit = 1;
		total = 1;
	}

	synchronized void flushAll(int txnum) {
		for (Buffer buff : bufferpool)
			if (buff.isModifiedBy(txnum))
				buff.flush();
	}

	synchronized Buffer pin(int blk) {
		Buffer buff = getBuffer(blk);
		if(buff == null) return null;
		buff.pin();
		return buff;
	}

	synchronized Buffer pinNew(PageFormatter fmtr) {
		Buffer buff = chooseUnpinnedBuffer();
		if (buff == null) return null;
		buff.alloc(fmtr);
		buff.pin();
		return buff;
	}

	/**
	 * Unpins the specified buffer.
	 * @param buff the buffer to be unpinned
	 */
	synchronized void unpin(Buffer buff) {
		buff.unpin();
		if (!buff.isPinned()){
			if( availableTail == null )
				availableHead = buff;
			else{
				buff.setPreAvailable(availableTail);
				availableTail.setNextAvailable(buff);
			}
			availableTail = buff;
		}
	}

	synchronized int unpinFree(Buffer buff) {
		unpin(buff);
		if (!buff.isPinned())
			return buff.free();
		return 0;
	}
	
	/**
	 * Returns the number of available (i.e. unpinned) buffers.
	 * @return the number of available buffers
	 */
	int available() {
		int number;
		Buffer ptr = availableHead;
		for(number = 0; ptr != null; ptr = ptr.getNextAvailable());
		return number;
	}

	private Buffer getBuffer(int blk) {
		Buffer avlBuf = availableHead;
		boolean exist = false;
		
		hit++;total++;
		
		for (Buffer buff : bufferpool) {
			int b = buff.block();
			if (b != -1 && b == blk){
				if(buff.isPinned())	return buff;
				avlBuf = buff;
				exist = true;
				break;
			}
		}
		
		if(chooseUnpinnedBuffer(avlBuf)!=null && !exist){
			avlBuf.assignToBlock(blk);
			hit--;
		}
		
		return avlBuf;
	}

	private Buffer chooseUnpinnedBuffer(Buffer buff) {
		if(buff == null)	return null;
		
		Buffer pre = buff.getPreAvailable();
		Buffer next = buff.getNextAvailable();
		
		buff.setNextAvailable(null);
		buff.setPreAvailable(null);
		
		if(next != null)	next.setPreAvailable(pre);
		else				availableTail = pre;
		if(pre != null)		pre.setNextAvailable(next);
		else				availableHead = next;
		
		return buff;
	}

	private Buffer chooseUnpinnedBuffer() {
		return chooseUnpinnedBuffer(availableHead);
	}
	
	void testShowState(int size){
		boolean ispinned;
		String print = "Buffer hole state:\t";
		Buffer b = availableHead;
		while(b != null){
			print += "(" + b.block() + ")->";
			b = b.getNextAvailable();
		}
		print += "X";
		System.out.println(print);

		System.out.print("Buffer using state:\n[");
		for(int i=0; i<size; i++){
			if(i%64 == 0 && i!=0) System.out.print("|\n|");
			
			ispinned = false;
			for(int j=0; j<bufferpool.length; j++){
				if(bufferpool[j].block() == i){
					ispinned = true;
					if(bufferpool[j].isPinned())
						System.out.print('*');
					else
						System.out.print('-');
					break;
				}
			}
			if(!ispinned) System.out.print(' ');
		}
		System.out.println(']');
		System.out.println("Current hit quote:\t"+(float)hit/total*100 + "%");
	}
}